/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "IMesh.h"
#include "core/core.h"
#include "dx/MeshGeometry.h"
#include "IModelBase.h"
#include "ILight.h"

#include <algorithm>

namespace model {

IRenderer* IMesh::s_pRenderer = NULL;
unsigned int IMesh::s_uiNumMeshes = 0;

ICamera* IMesh::s_pCam = NULL;

IMesh::IMesh(void)
:	sg::SceneNode(),
	m_bUseShaderEffect(false),
	m_pGeometry(NULL),
	m_sName(""),
	m_bMeshVisible(true),
	m_bBoundingVol(false),
	m_bTransparent(false),
	m_vTextureAtlasDiffuse(0,0,1,1),
	m_vTextureAtlasNormal(0,0,0,0),
	m_vTextureAtlasRefl(0,0,0,0),
	m_bTAHandlesInitialized(false),
	hTADiffuse(0),
	hTANormal(0),
	hTARefl(0),
	m_uiLOD(LODLEVEL-1)
{
	memset(&m_pMaterial, 0, sizeof(sComplexMaterial));
	memset(&m_KeyFrameInfo, 0, sizeof(KeyFrameInfo));

	if (!s_pRenderer )
		s_pRenderer = coreGetRenderer();

	if (!s_pCam)
		s_pCam = s_pRenderer->getCamera();
	
//	IModelBaseRenderer::AddMesh(this);

	++s_uiNumMeshes;
}

IMesh::~IMesh(void)
{
	if (m_pGeometry)
		m_pGeometry = dynamic_cast<IMeshGeometry*>( m_pGeometry->destroy() );

//	IModelBaseRenderer::RemoveMesh(this);

	--s_uiNumMeshes;
}

bool IMesh::operator<(const IMesh& rhs)const
{
	static math::Vector3 camPos;
	static float d1, d2;

 	camPos = coreGetRenderer()->getCamera()->getPosition();
	d1 = (m_bbSphere.getCenter()*getWorldTransformMatrix() - camPos).lengthSquared();
	d2 = (rhs.m_bbSphere.getCenter()*rhs.getWorldTransformMatrix() - camPos).lengthSquared();
	return d1 < d2;
}

bool IMesh::operator>(const IMesh& rhs)const
{
	static math::Vector3 camPos;
	static float d1, d2;
	camPos = coreGetRenderer()->getCamera()->getPosition();
	d1 = (m_bbSphere.getCenter() - camPos).lengthSquared();
	d2 = (rhs.m_bbSphere.getCenter() - camPos).lengthSquared();
	return d1 > d2;
}

unsigned int IMesh::GetMeshCount()
{ 
	return s_uiNumMeshes; 
}

sg::SceneNode* IMesh::rawcopy()
{
	IMesh *mesh = new IMesh();
	
	// Copy of the material
	mesh->m_pMaterial = this->m_pMaterial;
	
	// Reference to the geometry node
	mesh->m_pGeometry = this->m_pGeometry;
	mesh->m_pGeometry->addRef();

	// Set the name for this mesh
	mesh->m_sName = this->m_sName;
	mesh->m_bMeshVisible = this->m_bMeshVisible;

	mesh->m_bUseShaderEffect = this->m_bUseShaderEffect;

	mesh->calculateBoundingSphere( );
	return mesh;
}

void IMesh::setName(const std::string &name)
{
	m_sName = name;
}

const std::string& IMesh::getName() const
{
	return m_sName;
}

bool IMesh::load(FILE *file)
{
	bool isRefMesh = false;
	// NEW
	char ident[3];
	memset(ident, 0, 3);
	fread(ident, sizeof(char), 2, file);
	if (strcmp(ident, "D_") == 0) {
		m_pTransform = new DynamicTransform();
	}
	else if (strcmp(ident, "S_") == 0) {
		m_pTransform = new StaticTransform();
	} 
	else if (strcmp(ident, "R_") == 0) {// References are imported as dynamic objects
		isRefMesh = true;
		m_pTransform = new StaticTransform();
	}
	else if( strcmp(ident, "B_") == 0) { // if the mesh is an unvisible bounding box
		m_bBoundingVol = true;
		m_pTransform = new StaticTransform();
	} 
	else if( strcmp(ident, "T_") == 0) {  // if the mesh is transparent
		m_bTransparent = true;
		m_pTransform = new StaticTransform();
	}
	else 
		return false;

	//if (isRefMesh) {
		math::Matrix4 mat;
		fread((void*)&mat.m[0], sizeof(float), 16, file);
		dynamic_cast<StaticTransform*>(m_pTransform)->setMatrix(mat);
	/*} else {
		math::Vector3 pos;
		fread((void*)&pos.x, sizeof(float), 3, file);
		m_pTransform->setPosition(pos);
	}
*/
	if (coreGetRendererType() == CORE_DIRECTX) {
		m_pGeometry = new directx::MeshGeometry();
		m_pGeometry->addRef();
	}
	m_pGeometry->load( file );

	calculateBoundingSphere( );

	return true;
}

sComplexMaterial IMesh::getMaterial() 
{
	return m_pMaterial;
}

void IMesh::setMaterial(const sComplexMaterial &material, bool transparent)
{
	// And now do the tricky stuff. You got the material, check if it already exists. Add it to the renderer. and then set it or the one already in the render to this material
	m_pMaterial = material;
}

/**********************************************************************************************
* function for calculating the front-bottom-left and the back-top-right vertices of the BBox*
**********************************************************************************************/
void IMesh::calculateMinMax( math::Vector3 &minVec, math::Vector3 &maxVec)
{
	minVec = math::Vector3( 99999, 99999, 99999);
	maxVec= math::Vector3( -99999, -99999, -99999);

	for (unsigned int i = 0; i < m_pGeometry->m_uiNumVertices; ++i)
	{
		if ( m_pGeometry->m_pVertices[ i].position.x < minVec.x)
			minVec.x = m_pGeometry->m_pVertices[ i].position.x;

		if ( m_pGeometry->m_pVertices[ i].position.y < minVec.y)
			minVec.y = m_pGeometry->m_pVertices[ i].position.y;

		if ( m_pGeometry->m_pVertices[ i].position.z < minVec.z)
			minVec.z = m_pGeometry->m_pVertices[ i].position.z;

		if ( m_pGeometry->m_pVertices[ i].position.x > maxVec.x)
			maxVec.x = m_pGeometry->m_pVertices[ i].position.x;

		if ( m_pGeometry->m_pVertices[ i].position.y > maxVec.y)
			 maxVec.y = m_pGeometry->m_pVertices[ i].position.y;

		if ( m_pGeometry->m_pVertices[ i].position.z > maxVec.z)
			maxVec.z = m_pGeometry->m_pVertices[ i].position.z;
	}

	// Create a bounding box from that values
	m_bbBox.setSize(minVec, maxVec);
}


void IMesh::calculateBoundingSphere( )
{
	unsigned int curNumVertices = m_pGeometry->getNumVertices();
	unsigned int curNumFaces = m_pGeometry->getNumFaces();

	// hole den Pointer auf die Vertices:
	model::sMeshVertex  *pCurVertices = m_pGeometry->getMeshVertices();

	// berechne die Bounding Box des aktuellen Mesh 
	math::Vector3 Vec3MinVec;
	math::Vector3 Vec3MaxVec;

	// Calculate the bounding box
	calculateMinMax( Vec3MinVec, Vec3MaxVec);

	float centerX = ( Vec3MinVec.x + Vec3MaxVec.x) /2.0f;
	float centerY = ( Vec3MinVec.y + Vec3MaxVec.y) /2.0f;
	float centerZ = ( Vec3MinVec.z + Vec3MaxVec.z) /2.0f;

	//math::Vector3 *center = new math::Vector3( centerX, centerY, centerZ);
	math::Vector3 m_sphereCenter( centerX, centerY, centerZ);

	float m_sphereRadius = 0;
	
	for ( int i = 0; i < (int)curNumVertices; ++i)
	{
		float squareDistance =  ( ( (m_sphereCenter.x - pCurVertices[ i].position.x ) *  (m_sphereCenter.x - pCurVertices[ i].position.x ) ) + 
												 ( (m_sphereCenter.y - pCurVertices[ i].position.y ) *  (m_sphereCenter.y - pCurVertices[ i].position.y ) ) + 
												 ( (m_sphereCenter.z - pCurVertices[ i].position.z ) *  (m_sphereCenter.z - pCurVertices[ i].position.z ) ) );

		if ( m_sphereRadius < squareDistance)
			m_sphereRadius = squareDistance;

	}
	m_sphereRadius = sqrt( m_sphereRadius );

	m_bbSphere.setSize(m_sphereCenter, m_sphereRadius);

	if (pParent)
		pParent->updateBoundingSphere();
}

void IMesh::drawNodes(bool drawTransparent, math::Matrix4 *postMat )
{
	if (m_pMaterial.pEffect0) {
		if (m_pMaterial.pEffect0->bind()) {
			for (int i=0; i<m_pMaterial.pEffect0->getNumPasses(); ++i) {
				if (m_pMaterial.pEffect0->beginPass(i)) {
					drawMesh(postMat);
					m_pMaterial.pEffect0->endPass();
				}
			}
			m_pMaterial.pEffect0->unbind();
		}
	} else 
		drawMesh(postMat);
}

void IMesh::drawMesh(math::Matrix4 *postMat) 
{
	if (!m_bMeshVisible || !m_bVisible) return;

	static math::Vector3 camPos;
	static float dist;
	static math::Vector3 nullPos(0,0,0);

	s_pRenderer->pushMatrix();

	// Get the transformation either of the current interpolated matrix or the real one
	if (m_KeyFrameInfo.numFrames !=0)
		m_mWorldTransform = getWorldTransformMatrix(&m_KeyFrameInfo.currentMat);
	else 
		m_mWorldTransform = getWorldTransformMatrix();

	if (postMat)
		m_mWorldTransform *= *postMat;

	s_pRenderer->applyMatrix( m_mWorldTransform);
	
	/*math::Vector3 pos = m_mWorldTransform.getTranslation();
	math::Matrix4 mat;
	mat.setTranslation(pos);
	s_pRenderer->applyMatrix(mat);	*/

	if (m_pMaterial.pEffect0) {
		setCustomVariables();
		m_pMaterial.pEffect0->setVariables();
	} else {
		if (m_pMaterial.pTexture0)
			m_pMaterial.pTexture0->bind();
	}
	// Get the current LOD level (0 .. 3)
	camPos = s_pCam->getPosition();
	dist = camPos.sqrDistance( nullPos * m_mWorldTransform);
	

	// Draw the geometry set
	m_pGeometry->draw(m_uiLOD);

	if (!m_pMaterial.pEffect0) {
		if (m_pMaterial.pTexture0)
			m_pMaterial.pTexture0->unbind();
	}

	s_pRenderer->popMatrix();
}

void IMesh::setCustomVariables()
{
	if (!m_pMaterial.pEffect0) return;
	for (std::vector<sEffectData>::iterator it = m_lEffectData.begin(); it != m_lEffectData.end(); ++it) {
		switch (it->dataType) {
			case TYPE_FLOAT:
				m_pMaterial.pEffect0->setFloat( it->handle, (float*) it->data, it->numElements);
				break;
			case TYPE_TEXTURE:
				m_pMaterial.pEffect0->setTexture( it->handle, (ITexture*) it->data);
				break;
			case TYPE_MATRIX:
				m_pMaterial.pEffect0->setMatrix( it->handle, (math::Matrix4*) it->data);
		}
	}

	if (!m_bTAHandlesInitialized && m_pMaterial.pEffect0) {
		hTADiffuse = m_pMaterial.pEffect0->getParameterByName("TADiffuse");
		hTANormal  = m_pMaterial.pEffect0->getParameterByName("TANormal");
		hTARefl    = m_pMaterial.pEffect0->getParameterByName("TARefl");
		m_bTAHandlesInitialized = true;
	}
	if (hTADiffuse)
		m_pMaterial.pEffect0->setFloat(hTADiffuse, &m_vTextureAtlasDiffuse.x, 4);
	if (hTANormal)
		m_pMaterial.pEffect0->setFloat(hTANormal, &m_vTextureAtlasNormal.x, 4);
	if (hTARefl)
		m_pMaterial.pEffect0->setFloat(hTARefl, &m_vTextureAtlasRefl.x, 4);
}

void IMesh::setEnableShader(bool value)
{
	m_bUseShaderEffect = value;
}

const bool IMesh::isShaderEnabled() const
{
	return m_bUseShaderEffect;
}

IMeshGeometry* IMesh::getMeshGeometry() const
{
	return m_pGeometry;
}

const bool IMesh::isMeshVisible() const
{
	return m_bMeshVisible;
}

const bool IMesh::isBoundingVolume() const
{
	return m_bBoundingVol;
}

bool IMesh::addShaderInput(eEffectDataType datatype, const std::string &identifier, void *data, int size)
{
	sEffectData ed;
	memset(&ed, 0, sizeof(sEffectData));
	ed.handle = m_pMaterial.pEffect0->getParameterByName(identifier);
	if (ed.handle != NULL) {
		ed.dataType = datatype;
		ed.numElements = size;
		ed.data = data;
		m_lEffectData.push_back(ed);
		return true;
	}
	return false;
}

void IMesh::updateVisiblity(Frustum *frustum)
{
	SceneNode::updateVisiblity(frustum);
	if (isVisible()) {
		++m_pMaterial.uiVisibleMeshes;
	}
}

void IMesh::update(double dt)
{
	setCamDistanceSqr( s_vCamPos.sqrDistance( getWorldTransformMatrix().getTranslation() ) );
}

bool IMesh::isCentered() 
{
	return (m_pTransform->getMatrix().getTranslation() == math::Vector3(0,0,0));
}

void IMesh::setTextureAtlasInfo( int texNum, const math::Vector4 &values)
{
	switch (texNum) {
		case 0:
			m_vTextureAtlasDiffuse = values;
			break;
		case 1:
			m_vTextureAtlasNormal = values;
			break;
		case 2:
			m_vTextureAtlasRefl = values;
			break;
	}
}

const bool IMesh::isTransparent() const
{
	return m_bTransparent;
}

void IMesh::setTransparent(bool value) 
{
	m_bTransparent = value;
	sg::SceneNode *parent = getParent();
	// Add the mesh according to it's transparency
	parent->addChild( this, m_bTransparent );
	// Remove the mesh from whereever it was first
	parent->removeChild(this);
}

void IMesh::setCamDistanceSqr( float distance)
{
	static float maxDist = (800*800);
	m_uiLOD = static_cast<unsigned int>( max(0, min(LODLEVEL, LODLEVEL-((LODLEVEL*distance) / maxDist))) );
}

void IMesh::setKeyFrameInfo(const KeyFrameInfo &info)
{
	m_KeyFrameInfo = info;
}

void IMesh::updateKeyFrame(double frameFrac)
{
	if ( !m_bVisible) return;
	// Go through all the keyframes and set the matrix
	float t;
	int lastframe = 0;
	if (m_KeyFrameInfo.numFrames != 0) {
		for (int i=0; i< m_KeyFrameInfo.numFrames; ++i) {
			if (m_KeyFrameInfo.frames[i].frame < static_cast<int>(frameFrac)+1) {
				lastframe = i;
			}
		}

		if (lastframe == m_KeyFrameInfo.numFrames-1){
			// No more keyframes, we don't need to interpolate
			m_KeyFrameInfo.currentMat = m_KeyFrameInfo.frames[lastframe].matrix;	// First, set the current matrix
		} else  {
			// First of all: What is t?
			float diff = static_cast<float>( m_KeyFrameInfo.frames[lastframe+1].frame - m_KeyFrameInfo.frames[lastframe].frame );
			t = 1.0f-static_cast<float>(m_KeyFrameInfo.frames[lastframe+1].frame-frameFrac) /diff;
			// Interpolate
			m_KeyFrameInfo.currentMat.interpolate(m_KeyFrameInfo.frames[lastframe].matrix, m_KeyFrameInfo.frames[lastframe+1].matrix, t);
			//m_KeyFrameInfo.currentMat = m_KeyFrameInfo.frames[lastframe].matrix;
		}
	}
}

} // namespace
